#include <seccomp.h>
#include <errno.h>
#include <string.h>

#include <sys/ioctl.h>


enum {
	PLEDGE_STDIO        = 1 << 0,
	PLEDGE_RPATH        = 1 << 1,
	PLEDGE_WPATH        = 1 << 2,
	PLEDGE_CPATH        = 1 << 3,
	PLEDGE_DPATH        = 1 << 4,
	PLEDGE_TMPPATH      = 1 << 5,
	PLEDGE_INET         = 1 << 6,
	PLEDGE_FATTR        = 1 << 7,
	PLEDGE_FLOCK        = 1 << 8,
	PLEDGE_UNIX         = 1 << 9,
	PLEDGE_DNS          = 1 << 10,
	PLEDGE_GETPW        = 1 << 11,
	PLEDGE_SENDFD       = 1 << 12,
	PLEDGE_RECVFD       = 1 << 13,
	PLEDGE_IOCTL        = 1 << 14,
	PLEDGE_TTY          = 1 << 15,
	PLEDGE_PROC         = 1 << 16,
	PLEDGE_EXEC         = 1 << 17,
	PLEDGE_PROT_EXEC    = 1 << 18,
	PLEDGE_SETTIME      = 1 << 19,
	PLEDGE_PS           = 1 << 20,
	PLEDGE_VMINFO       = 1 << 21,
	PLEDGE_ID           = 1 << 22,
	PLEDGE_PF           = 1 << 23,
	PLEDGE_AUDIO        = 1 << 24,
};

typedef struct {
	char *name;
	int mask;
} promise;

promise ptable[] = {
	{ .name = "stdio",      .mask = PLEDGE_STDIO },
	{ .name = "rpath",      .mask = PLEDGE_RPATH },
	{ .name = "wpath",      .mask = PLEDGE_WPATH },
	{ .name = "cpath",      .mask = PLEDGE_CPATH },
	{ .name = "dpath",      .mask = PLEDGE_DPATH },
	{ .name = "tmppath",    .mask = PLEDGE_TMPPATH },
	{ .name = "inet",       .mask = PLEDGE_INET },
	{ .name = "fattr",      .mask = PLEDGE_FATTR },
	{ .name = "flock",      .mask = PLEDGE_FLOCK },
	{ .name = "unix",       .mask = PLEDGE_UNIX },
	{ .name = "dns",        .mask = PLEDGE_DNS },
	{ .name = "getpw",      .mask = PLEDGE_GETPW },
	{ .name = "sendfd",     .mask = PLEDGE_SENDFD },
	{ .name = "recvfd",     .mask = PLEDGE_RECVFD },
	{ .name = "ioctl",      .mask = PLEDGE_IOCTL },
	{ .name = "tty",        .mask = PLEDGE_TTY },
	{ .name = "proc",       .mask = PLEDGE_PROC },
	{ .name = "exec",       .mask = PLEDGE_EXEC },
	{ .name = "prot_exec",  .mask = PLEDGE_PROT_EXEC },
	{ .name = "settime",    .mask = PLEDGE_SETTIME },
	{ .name = "ps",         .mask = PLEDGE_PS },
	{ .name = "vminfo",     .mask = PLEDGE_VMINFO },
	{ .name = "id",         .mask = PLEDGE_ID },
	{ .name = "pf",         .mask = PLEDGE_PF },
	{ .name = "audio",      .mask = PLEDGE_AUDIO },
};

int
pledge(const char *promises, const char *paths[])
{
	int i, n, f;
	int flags = 0;

        int rc = -1;
        scmp_filter_ctx ctx;

        ctx = seccomp_init(SCMP_ACT_TRAP);
        if (!ctx) {
		errno = EACCES;
		goto out;
	}

	while (*promises) {
		// skip spaces
		while (*promises && *promises == ' ') promises++;

		// look for a token
		f = 0;
		for (i = 0; i < sizeof(ptable)/sizeof(*ptable); i++) {
			n = strlen(ptable[i].name);
			if (!strncmp(promises, ptable[i].name, n)) {
				// this can be removed once every promise has been implemented
				if (!ptable[i].mask) {
					errno = ENOSYS;
					goto out;
				}
				flags |= ptable[i].mask;
				promises += n;
				f = 1;
				break;
			}
		}

		// what we saw was not any valid token
		if (!f) {
			errno = EINVAL;
			goto out;
		}

		// ensure the token is terminated by a space or end of string
		if (*promises && *promises != ' ') {
			errno = EINVAL;
			goto out;
		}
	}

#define RULE(syscall) \
        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(syscall), 0); \
        if (rc < 0) goto out

	// everyone is allowed to exit
	RULE(exit_group);

	if (flags & PLEDGE_STDIO) {
        RULE(clock_getres);
        RULE(clock_gettime);
        RULE(close);
/*        RULE(closefrom);  */
        RULE(dup);
        RULE(dup2);
        RULE(dup3);
        RULE(fchdir);
        RULE(fcntl);
        RULE(fstat);
        RULE(fsync);
        RULE(ftruncate);
        RULE(getdents);
/*        RULE(getdtablecount);     */
        RULE(getegid);
/*        RULE(getentropy);         */
        RULE(geteuid);
        RULE(getgid);
        RULE(getgroups);
        RULE(getitimer);
/*        RULE(getlogin);           */
        RULE(getpgid);
        RULE(getpgrp);
        RULE(getpid);
        RULE(getppid);
        RULE(getresgid);
        RULE(getresuid);
        RULE(getrlimit);
        RULE(getsid);
/*        RULE(getthrid);           */
        RULE(gettimeofday);
        RULE(getuid);
        RULE(getuid);
/*        RULE(issetugid);          */
/*        RULE(kevent);             */
/*        RULE(kqueue);             */
        RULE(lseek);
        RULE(madvise);
/*        RULE(minherit);           */
        RULE(mmap);
        RULE(mprotect);
/*        RULE(mquery);             */
        RULE(munmap);
        RULE(nanosleep);
        RULE(pipe);
        RULE(pipe2);
        RULE(poll);
/*        RULE(pread);              */
        RULE(preadv);
/*        RULE(pwrite);             */
        RULE(pwritev);
        RULE(read);
        RULE(readv);
        RULE(recvfrom);
        RULE(recvmsg);
        RULE(select);
        RULE(sendmsg);
/*        RULE(sendsyslog);         */
    /* sendto: only if dest sockaddr is NULL */
        RULE(sendto);
        RULE(setitimer);
        RULE(shutdown);
        RULE(sigaction);
        RULE(sigprocmask);
        RULE(sigreturn);
        RULE(socketpair);
        RULE(umask);
        RULE(wait4);
        RULE(write);
        RULE(writev);
	}
	if (flags & PLEDGE_RPATH) {
    /* Allowed if the only cause read-only effects on file system */
        RULE(chdir);
        RULE(getcwd);
        RULE(openat);
/*        RULE(fstatat);            */
        RULE(faccessat);
        RULE(readlinkat);
        RULE(lstat);
        RULE(chmod);
        RULE(fchmod);
        RULE(fchmodat);
/*        RULE(chflags);            */
/*        RULE(chflagsat);          */
        RULE(chown);
        RULE(fchown);
        RULE(fchownat);
        RULE(fstat);
/*        RULE(getfsstat);          */
    }
	if (flags & PLEDGE_WPATH) {
    /* system calls may have write effects on file system */
        RULE(getcwd);
        RULE(openat);
/*        RULE(fstatat);            */
        RULE(faccessat);
        RULE(readlinkat);
        RULE(lstat);
        RULE(chmod);
        RULE(fchmod);
        RULE(fchmodat);
/*        RULE(chflags);            */
/*        RULE(chflagsat);          */
        RULE(chown);
        RULE(fchown);
        RULE(fchownat);
        RULE(fstat);
    }
	if (flags & PLEDGE_CPATH) {
    /* system calls may create new files or directories on file system */
        RULE(rename);
        RULE(rmdir);
        RULE(renameat);
        RULE(link);
        RULE(linkat);
        RULE(symlink);
        RULE(unlink);
        RULE(unlinkat);
        RULE(mkdir);
        RULE(mkdirat);
    }
	if (flags & PLEDGE_DPATH) {
    /* may create special files */
/*        RULE(mkfifo);             */
        RULE(mknod);

    }
	if (flags & PLEDGE_TMPPATH) {
    /* system calls permitted to read, write, and create in /tmp directory */
        RULE(lstat);
        RULE(chmod);
/*        RULE(chflags);            */
        RULE(chown);
        RULE(unlink);
        RULE(fstat);

    }
	if (flags & PLEDGE_INET) {
    /* system calls may operate in AF_INET and AF_INET6 domains */
        RULE(socket);
        RULE(listen);
        RULE(bind);
        RULE(connect);
        RULE(accept4);
        RULE(accept);
        RULE(getpeername);
        RULE(getsockname);
    /* setsockopt: substantially reduced in functionality */
        RULE(setsockopt);
        RULE(getsockopt);

    }
	if (flags & PLEDGE_FATTR) {
    /* system calls may make explicit changes in struct stat of a file */
        RULE(socket);
        RULE(listen);
        RULE(bind);
        RULE(connect);
        RULE(accept4);
        RULE(accept);
        RULE(getpeername);
        RULE(getsockname);
        RULE(setsockopt);
        RULE(getsockopt);

    }
	if (flags & PLEDGE_FLOCK) {
    /* no distinction btw shared/exclusive locks; required for lock/unlock */
        RULE(fcntl);
        RULE(flock);
/*        RULE(lockf);              */
        RULE(open);

    }
	if (flags & PLEDGE_UNIX) {
    /* system calls may operate in AF_UNIX domain */
        RULE(socket);
        RULE(listen);
        RULE(bind);
        RULE(connect);
        RULE(accept4);
        RULE(accept);
        RULE(getpeername);
        RULE(getsockname);
        RULE(setsockopt);
        RULE(getsockopt);

    }
	if (flags & PLEDGE_DNS) {
    /* subsequent to successful open of /etc/resolv.conf; allow DNS xaction */
        RULE(sendto);
        RULE(recvfrom);
        RULE(socket);
        RULE(connect);

    }
	if (flags & PLEDGE_GETPW) {
        /* ro opening of files in /etc */
/*        RULE(getpwnam);           */
/*        RULE(getgrnam);           */
/*        RULE(getgrouplist);       */
/*        RULE(initgroups);     */

    }
	if (flags & PLEDGE_SENDFD) {
    /* directory FDs not permitted */
        RULE(sendmsg);

    }
	if (flags & PLEDGE_RECVFD) {
    /* directory FDs not permitted */
        RULE(recvmsg);

    }
	if (flags & PLEDGE_IOCTL) {
    /* allows subset of ioctl:
     * FIOCLEX
     * FIONCLEX
     * FIOASYNC
     * FIOGETOWN
     * FIOSETOWN
     * TIOCGETA succeed on tty device, otherwise fail with EPERM
     * TIOCGPGRP and TIOCGWINSZ allowed on tty device
     * Rather than test for tty device, just let ioctl fail on ENOTTY
     * A few other operations are allowed, but not listed here (?)
     */
        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
                SCMP_A1(SCMP_CMP_EQ, FIOCLEX));
        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
                SCMP_A1(SCMP_CMP_EQ, FIONCLEX));
        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
                SCMP_A1(SCMP_CMP_EQ, FIOASYNC));
/*        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
                SCMP_A1(SCMP_CMP_EQ, FIOGETOWN));   */
/*        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
                SCMP_A1(SCMP_CMP_EQ, FIOSETOWN));   */
/*        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 2,
                SCMP_A1(SCMP_CMP_EQ, TIOCGETA));    */
        rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 2,
                SCMP_A1(SCMP_CMP_EQ, TIOCGPGRP));

    }
	if (flags & PLEDGE_TTY) {
    /* allows subset of ioctl:
     * TIOCSPGRP
     * TIOCGETA
     * TIOCGPGRP
     * TIOCGWINSZ
     * TIOCSWINSZ
     * TIOCSBRK
     * TIOCCDTR
     * TIOCSETA
     * TIOCSETAW
     * TIOCSETAF
     */
     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCSPGRP));
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCGETA));    */
     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCGPGRP));
     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCGWINSZ));
     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCSWINSZ));
     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCSBRK));
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCCDTR));    */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCSETA));    */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCSETAW));   */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, TIOCSETAF));   */
    /* Also, allow r/w operations on /dev/tty */
    }
	if (flags & PLEDGE_TTY & PLEDGE_RPATH) {
/*        RULE(revoke);                 */
    }
	if (flags & PLEDGE_PROC) {
        RULE(fork);
        RULE(vfork);
        RULE(kill);
        RULE(getpriority);
        RULE(setpriority);
        RULE(setrlimit);
        RULE(setpgid);
        RULE(setsid);

    }
	if (flags & PLEDGE_EXEC) {
        RULE(execve);

    }
	if (flags & PLEDGE_PROT_EXEC) {
    /* Allows use of PROT_EXEC with the following system calls */
        RULE(mmap);
        RULE(mprotect);

    }
	if (flags & PLEDGE_SETTIME) {
    /* Allows setting of system time via the following system calls */
        RULE(settimeofday);
/*        RULE(adjtime);                */
/*        RULE(and adjfreq);            */

    }
	if (flags & PLEDGE_PS) {
    /* Allows sufficient sysctl access for inspection of procs, as in ps */
/*        RULE(sysctl);                 */

    }
	if (flags & PLEDGE_VMINFO) {
    /* Allows sufficient sysctl access for inspection of virtual mem,
     * as in top, vmstat */
/*        RULE(sysctl);                 */

    }
	if (flags & PLEDGE_ID) {
        RULE(setuid);
/*        RULE(seteuid);                */
        RULE(setreuid);
        RULE(setresuid);
        RULE(setgid);
/*        RULE(setegid);                */
        RULE(setregid);
        RULE(setresgid);
        RULE(setgroups);
/*        RULE(setlogin);               */
        RULE(setrlimit);
        RULE(getpriority);
        RULE(setpriority);

    }
	if (flags & PLEDGE_PF) {
    /* Allows the following subset of ioctl operations on the pf(4) device:
     * DIOCADDRULE
     * DIOCGETSTATUS
     * DIOCNATLOOK
     * DIOCRADDTABLES
     * DIOCRCLRADDRS
     * DIOCRCLRTABLES
     * DIOCRCLRTSTATS
     * DIOCRGETTSTATS
     * DIOCRSETADDRS
     * DIOCXBEGIN
     * DIOCXCOMMIT
     */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCADDRULE));     */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCGETSTATUS));   */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCNATLOOK));     */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCRADDTABLES));  */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCRCLRADDRS));   */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCRCLRTABLES));  */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCRCLRTSTATS));  */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCRGETTSTATS));  */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCRSETADDRS));   */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, DIOCXBEGIN));  */

    }
	if (flags & PLEDGE_AUDIO) {
    /* Allows the following subset of ioctl operations on the audio(4) device:
     * AUDIO_GETPOS
     * AUDIO_SETINFO
     * AUDIO_GETINFO
     * AUDIO_GETENC
     * AUDIO_SETFD
     * AUDIO_GETPROPS
     */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, AUDIO_GETPOS));    */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, AUDIO_SETINFO));   */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, AUDIO_GETINFO));   */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, AUDIO_GETENC));    */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, AUDIO_SETFD));     */
/*     rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1,
        SCMP_A1(SCMP_CMP_EQ, AUDIO_GETPROPS));  */

    }

        rc = seccomp_load(ctx);
        if (rc < 0) goto out;

	rc = 0;

out:
        seccomp_release(ctx);
        return rc;
}
